#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_err.h"
#include "i2c_custom.h"
#include "max30102.h"

static const char *TAG = "max30102";

// Configuração dos registradores do modo de funcionamento do sensor MAX30102. Basta colocar o valor binário da configuração que o compilador fará o trabalho de setar os bits corretos.
static max_config max30102_configuration = {

    .INT_EN_1.A_FULL_EN = 1,
    .INT_EN_1.PPG_RDY_EN = 1,
    .INT_EN_1.ALC_OVF_EN = 0,
    .INT_EN_1.PROX_INT_EN = 0,

    .INT_EN_2.DIE_TEMP_RDY_EN = 0,

    .FIFO_WRITE_PTR.FIFO_WR_PTR = 0,

    .OVEF_COUNTER.OVF_COUNTER = 0,

    .FIFO_READ_PTR.FIFO_RD_PTR = 0,

    .FIFO_CONF.SMP_AVE = 0b010,      // média de 4 valores
    .FIFO_CONF.FIFO_ROLLOVER_EN = 1, // fifo rollover enable
    .FIFO_CONF.FIFO_A_FULL = 0,      // 0

    .MODE_CONF.SHDN = 0,
    .MODE_CONF.RESET = 0,
    .MODE_CONF.MODE = 0b011, // SPO2 mode

    .SPO2_CONF.SPO2_ADC_RGE = 0b01, // 16384 nA(Escala do DAC)
    .SPO2_CONF.SPO2_SR = 0b001,     // 100 samples per second
    .SPO2_CONF.LED_PW = 0b10,       // pulso de 215 uS do led.

    .LED1_PULSE_AMP.LED1_PA = 0x18, // CORRENTE DO LED1 25.4mA
    .LED2_PULSE_AMP.LED2_PA = 0x18, // CORRENTE DO LED2 25.4mA

    .PROX_LED_PULS_AMP.PILOT_PA = 0X7F,

    .MULTI_LED_CONTROL1.SLOT2 = 0, // Desabilitado
    .MULTI_LED_CONTROL1.SLOT1 = 0, // Desabilitado

    .MULTI_LED_CONTROL2.SLOT4 = 0, // Desabilitado
    .MULTI_LED_CONTROL2.SLOT3 = 0, // Desabilitado
};

/**
 * @brief max30102_write_reg
 *
 * @param i2c_num
 * @param device_address
 * @param write_buffer
 * @param write_size
 * @return esp_err_t
 */
static esp_err_t max30102_write_reg(i2c_port_t i2c_num, uint8_t device_address, uint8_t write_reg, uint8_t write_buffer)
{
    uint8_t write_data[2] = {write_reg, write_buffer};
    return i2c_master_write_to_device(i2c_num, device_address, write_data, 2, portTICK_PERIOD_MS);
}

/**
 * @brief max30102_read_reg
 *
 * @param i2c_num
 * @param device_address
 * @param read_reg
 * @param read_buffer
 * @param read_size
 * @return esp_err_t
 */
esp_err_t max30102_read_reg(i2c_port_t i2c_num, uint8_t device_address, uint8_t read_reg, uint8_t *read_buffer, size_t read_size)
{
    return i2c_master_write_read_device(i2c_num, device_address, &read_reg, 1, read_buffer, read_size, portTICK_PERIOD_MS);
}

/**
 * @brief max30102_int_gpio_init
 *
 * @return esp_err_t
 */
esp_err_t max30102_int_gpio_init(void)
{
    return gpio_set_direction(MAX30102_INT_PIN, GPIO_MODE_INPUT);
}

/**
 * @brief max30102_get_int_gpio_level
 *
 * @return true
 * @return false
 */
bool max30102_get_int_gpio_level(void)
{
    return gpio_get_level(MAX30102_INT_PIN);
}

/**
 * @brief max30102_i2c_master_init
 *
 * @param port
 * @param sda
 * @param scl
 * @return esp_err_t
 */
esp_err_t max30102_i2c_master_init(i2c_port_t port, int sda, int scl)
{
    i2c_config_t i2c_config = i2c_conf_func();
    // i2c_param_config(I2C_NUM_0, &i2c_config);
    // i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
    i2c_param_config(port, &i2c_config);
    return i2c_driver_install(port, I2C_MODE_MASTER, 0, 0, 0);
}

/**
 * @brief max30102_reset
 *
 * @return esp_err_t
 */
esp_err_t max30102_reset(void)
{
    return max30102_write_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_MODE_CONFIG, 0x40);
}

/**
 * @brief max30102_clear_status
 *
 * @return esp_err_t
 */
esp_err_t max30102_clear_status(void)
{
    uint8_t status_temp;

    if (max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_INTR_STATUS_1, &status_temp, 1) != ESP_OK)
    {
        ESP_LOGW(TAG, "max30102 status_1 err. status_1[%d]", status_temp);
        return ESP_FAIL;
    }
    if (max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_INTR_STATUS_2, &status_temp, 1) != ESP_OK)
    {
        ESP_LOGW(TAG, "max30102 status_2 err. status_2[%d]", status_temp);
        return ESP_FAIL;
    }

    return ESP_OK;
}

/**
 * @brief max30102_init
 *
 * @return esp_err_t
 */
esp_err_t max30102_init(void)
{
    esp_err_t ret;
    uint8_t write_reg[13], init_cmd[13];

    write_reg[0] = REG_INTR_ENABLE_1;
    init_cmd[0] = max30102_configuration.data1; /* INTR1 set Gting */
    write_reg[1] = REG_INTR_ENABLE_2;
    init_cmd[1] = max30102_configuration.data2; /* INTR2 set Gting */
    write_reg[2] = REG_FIFO_WR_PTR;
    init_cmd[2] = max30102_configuration.data3; /* FIFO_WR_PTR[4:0] */
    write_reg[3] = REG_OVF_COUNTER;
    init_cmd[3] = max30102_configuration.data4; /* OVF_COUNTER[4:0] */
    write_reg[4] = REG_FIFO_RD_PTR;
    init_cmd[4] = max30102_configuration.data5; /* FIFO_RD_PTR[4:0] */
    write_reg[5] = REG_FIFO_CONFIG;
    init_cmd[5] = max30102_configuration.data6; /* sample avg = 1, fifo rollover=false, fifo almost full = 17 */
    write_reg[6] = REG_MODE_CONFIG;
    init_cmd[6] = max30102_configuration.data7; /* 0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED */
    write_reg[7] = REG_SPO2_CONFIG;
    init_cmd[7] = max30102_configuration.data8; /* SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS) */
    write_reg[8] = REG_LED1_PA;
    init_cmd[8] = max30102_configuration.data9; /* Choose value for ~ 7mA for LED1 */
    write_reg[9] = REG_LED2_PA;
    init_cmd[9] = max30102_configuration.data10; /* Choose value for ~ 7mA for LED2 */
    write_reg[10] = REG_PILOT_PA;
    init_cmd[10] = max30102_configuration.data11; /* Choose value for ~ 25mA for Pilot LED */
    write_reg[11] = REG_MULTI_LED_CTRL1;
    init_cmd[11] = max30102_configuration.data12;
    write_reg[12] = REG_MULTI_LED_CTRL2;
    init_cmd[12] = max30102_configuration.data13;

    /* write init data start */
    for (uint8_t i = 0; i < 13; i++)
    {
        ret = max30102_write_reg(I2C_MASTER_NUM, MAX30102_ADDR, write_reg[i], init_cmd[i]);
        if (ret != ESP_OK)
        {
            ESP_LOGE(TAG, "max30102 init err write_reg[%d]", i);
            return ESP_FAIL;
        }
    }
    ESP_LOGI(TAG, "max30102 init successfully");
    /* write init data end */

    /* clear status start */
    ret |= max30102_clear_status();
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "max30102 clear status fail.");
        return ESP_FAIL;
    }
    /* clear status end */

    return ESP_OK;
}

/**
 * @brief max30102_read_fifo
 *
 * @param red_data
 * @param ir_data
 * @return esp_err_t
 */
esp_err_t max30102_read_fifo(uint32_t *red_data, uint32_t *ir_data)
{
    esp_err_t ret = ESP_OK;
    uint8_t fifo_data[6];

    *red_data = 0;
    *ir_data = 0;

    /* fifo ready start */
    max30102_write_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_FIFO_WR_PTR, 0x00);
    max30102_write_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_OVF_COUNTER, 0x00);
    max30102_write_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_FIFO_RD_PTR, 0x00);
    /* fifo ready end */

    /* read fifo data start */
    if (max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_FIFO_DATA, fifo_data, 6) != ESP_OK)
    {
        ESP_LOGW(TAG, "max30102 fifo data read err.");
        return ESP_FAIL;
    }
    /* read fifo data end */

    /* handle read buff start */
    *ir_data = (fifo_data[0] << 16 | fifo_data[1] << 8 | fifo_data[2]) & 0x03FFFF;  /* Mask MSB [23:18] */
    *red_data = (fifo_data[3] << 16 | fifo_data[4] << 8 | fifo_data[5]) & 0x03FFFF; /* Mask MSB [23:18] */
    /* handle read buff end */

    /* clear status start */
    if (max30102_clear_status() != ESP_OK)
    {
        return ESP_FAIL;
    }
    /* clear status end */

    return ESP_OK;
}

/**
 * @brief max30102_get_temp
 *
 * @return float
 */
float max30102_get_temp(void)
{
    uint8_t int_temp;
    uint8_t decimal_temp;
    float max30102_temp = 0;

    /* read max30102 temp start */
    max30102_write_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_TEMP_CONFIG, 1);
    max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_TEMP_INTR, &int_temp, 1);
    max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_TEMP_FRAC, &decimal_temp, 1);
    /* read max30102 temp end */

    /* handle max30102 temp start */
    max30102_temp = (int_temp + ((float)(decimal_temp & 0x0F) / 10));
    /* handle max30102 temp end */

    /* clear status start */
    if (max30102_clear_status() != ESP_OK)
    {
        return ESP_FAIL;
    }
    /* clear status end */

    return max30102_temp;
}

/**
 * @brief max30102_get_samples_num
 *
 * @return uint8_t
 */
uint8_t max30102_get_samples_num(void)
{
    uint8_t samples_WR_PTR;
    uint8_t samples_RD_PTR;

    /* read start */
    max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_FIFO_WR_PTR, &samples_WR_PTR, 1);
    max30102_read_reg(I2C_MASTER_NUM, MAX30102_ADDR, REG_FIFO_RD_PTR, &samples_RD_PTR, 1);
    /* read end */

#if MAX30102_DEBUG
    ESP_LOGI(TAG, "samples_WR_PTR:%d   samples_RD_PTR:%d   samples_available_num:%d", samples_WR_PTR, samples_RD_PTR, samples_WR_PTR - samples_RD_PTR);
#endif

    if (samples_WR_PTR - samples_RD_PTR < 0)
    {
        return (samples_WR_PTR - samples_RD_PTR) + 32;
    }
    else
    {
        return (samples_WR_PTR - samples_RD_PTR);
    }
}
